package model;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import dao.DaoException;
import dao.DaoFactory;

/**
 * The application's main model
 * @author MUSAB
 *
 * @version $Revision: 1.0 $
 */
public class AppModel {
	
	// System configurations
	private Properties config;
	
	// Server connection
	public Socket serverSocket;
	
	// User object for the application
	private User user;
	
	// Currently active folder
	private int currentFolderId;
	
	private boolean isConnected=false;
	
	private ObjectInputStream inStream;
	private ObjectOutputStream osStream;
	
	
	/**
	 * Constructor for AppModel.
	 * @param config Properties
	 */
	public AppModel(Properties config){
		serverSocket = null;
		currentFolderId = -1;
		this.config = config;
	}
	
	/**
	 * Returns the server IP address and port number 
	 * in a hashmap. 
	 * 
	 * @return HashMap containing server IP and port
	 */
	public HashMap<String, String> getServerProperties(){

		HashMap<String, String> serverProperties = new HashMap<String, String>();
		for(String key : config.stringPropertyNames()) {
		  String value = config.getProperty(key);
		  if (key.equalsIgnoreCase("server.ip")){
			  serverProperties.put("ip", value);
		  }
		  if (key.equalsIgnoreCase("server.port")){
			  serverProperties.put("port", value);
		  }
		}
		return serverProperties;
	}
	
	/**
	 * Initializes DAO so that it creates the persistent layer if not exists
	
	 * @throws DaoException  */
	public void initDao() throws DaoException{
		try {
			new DaoFactory();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Try to load the model for the given user name.
	 * @param username
	
	 * @return boolean value indicating a successful or failed load operation */
	public boolean loadAccount(String username){
		User user = new User(username);
		try {
			if (user.load(username) == true){
				
				// Load 
				this.user = user;
				
				// Load all his folders and its emails.
				loadFolders();
				
				// load user tokens
				loadTokens();
				
				// load user templates
				loadTemplates();
				
				// load user meetings 
				loadMeetings();
				
				// load user filters 
				loadFilters();
				
				// the application is ready
				return true;
			}
		} catch (ModelException e) {
			return false;
		}
		return false;
	}
	
	/**
	 * Returns a hashmap of system tokens as key => description
	 * 
	
	 * @return System tokens hashmap */
	public HashMap<String, String> getSystemDefinedTokens(){
		
		HashMap<String, String> tokens = new HashMap<String, String>();
		for(String key : config.stringPropertyNames()) {
		  String value = config.getProperty(key);
		  if (key.contains("systoken.")){
			  tokens.put(key.substring(key.lastIndexOf(".")+1), value);
		  }
		}
		return tokens;
	}
	
	/**
	 * Tries to create a new user given a user name
	 * @param username
	
	 * @throws ModelException  */
	public void createAccount(String username) throws ModelException
	{

		// Create user
		User user = new User(username);
		this.user = user;
		user.save();
		
		// Create his basic folders
		user.createNewFolder("Inbox", 0);
		user.createNewFolder("Sent", 0);
		user.createNewFolder("Drafts", 0);
		user.createNewFolder("Outbox", 0);
		
	}
	
	/**
	 * Loads the folders
	
	
	 * @return boolean value indicating a successful or failed folder load operation * @throws ModelException  */
	public boolean loadFolders() throws ModelException {
		HashMap<Integer, String[]> userFolder = null;
		try {
			userFolder = DaoFactory.getFolderDao().findByUserId(user.getId());
			for (Map.Entry<Integer, String[]> entry : userFolder.entrySet()) {
				String folder[] = entry.getValue();
				Folder folderObj = new Folder(folder[0], Integer.parseInt(folder[1]), Integer.parseInt(folder[2]));
				folderObj.setId(entry.getKey());
				loadEmails(folderObj);
				user.getFolders().add(folderObj);
			}
		} catch (DaoException e) {
			throw new ModelException(e.getMessage());
		}

		return true;
	}
	
	/**
	 * load the emails for a given folder object
	
	
	
	 * @param folder Folder
	 * @return boolean value indicating a successful or failed load operation. * @throws DaoException
	 * @throws ModelException  */
	public boolean loadEmails(Folder folder) throws DaoException {
		Map<Integer, String[]> mail = null;
		try {
			mail = DaoFactory.getEmailDao().findByFolderId(folder.getId());
			for (Map.Entry<Integer, String[]> entryMail : mail.entrySet()) {
				String mailArray[] = entryMail.getValue();

				Email emailObj = new EmailBuilder()
									.setTo(mailArray[0])
									.setFrom(mailArray[1])
									.setSubject(mailArray[2])
									.setBody(mailArray[3])
									.setRead(Boolean.valueOf(mailArray[4]))
									.setTimestamp(new Timestamp(Long.valueOf(mailArray[5])))									
									.setFolderId(Integer.valueOf(mailArray[6]))
									.setId(entryMail.getKey())
									.build();
				folder.getEmails().add(emailObj);
			}
		} catch (DaoException e) {
			throw new DaoException(e.getMessage());
		}
		return true;
	}

	/**
	 * Loads the tokens
	
	
	 * @return boolean value indicating a successful or failed tokens load operation * @throws ModelException  */
	public boolean loadTokens() throws ModelException {
		HashMap<Integer, String[]> userToken = null;
		try {
			userToken = DaoFactory.getTokenDao().findByUserId(user.getId());
			for (Map.Entry<Integer, String[]> entry : userToken.entrySet()) {
				String token[] = entry.getValue();
				Token tokenObj = new Token(Integer.parseInt(token[0]), token[1], token[2]);
				tokenObj.setId(entry.getKey());
				user.getTokens().add(tokenObj);
			}
		} catch (DaoException e) {
			throw new ModelException(e.getMessage());
		}

		return true;
	}
	
	/**
	 * Loads the templates
	
	
	 * @return boolean value indicating a successful or failed templates load operation * @throws ModelException  */
	public boolean loadTemplates() throws ModelException {
		HashMap<Integer, String[]> userTemplate = null;
		try {
			userTemplate = DaoFactory.getTemplateDao().findByUserId(user.getId());
			for (Map.Entry<Integer, String[]> entry : userTemplate.entrySet()) {
				String template[] = entry.getValue();
				Template templateObj = new Template(Integer.parseInt(template[0]), template[1], template[2]);
				templateObj.setId(entry.getKey());
				user.getTemplates().add(templateObj);
			}
		} catch (DaoException e) {
			throw new ModelException(e.getMessage());
		}

		return true;
	}
	
	/**
	 * Loads the meetings
	
	
	 * @return boolean value indicating a successful or failed meetings load operation * @throws ModelException  */
	public boolean loadMeetings() throws ModelException {
		HashMap<Integer, String[]> userMeeting = null;
		try {
			userMeeting = DaoFactory.getMeetingDao().findByUserId(user.getId());
			for (Map.Entry<Integer, String[]> entry : userMeeting.entrySet()) {
				String meeting[] = entry.getValue();
				Meeting meetingObj = new MeetingBuilder()
										.setUserId(Integer.parseInt(meeting[0]))
										.setTopic(meeting[1])
										.setLocation(meeting[2])
										.setStart(new Timestamp(Long.valueOf(meeting[3])))
										.setEnd(new Timestamp(Long.valueOf(meeting[4])))
										.setAttendees(meeting[5])
										.setDescription(meeting[6])
										.build();				
				meetingObj.setId(entry.getKey());
				user.getMeetings().add(meetingObj);
			}
		} catch (DaoException e) {
			throw new ModelException(e.getMessage());
		}

		return true;
	}
	
	/**
	 * Loads the filters
	
	
	 * @return boolean value indicating a successful or failed filters load operation * @throws ModelException  */
	public boolean loadFilters() throws ModelException {
		HashMap<Integer, String[]> userFilters = null;
		try {
			userFilters = DaoFactory.getFilterDao().findByUserId(user.getId());
			for (Map.Entry<Integer, String[]> entry : userFilters.entrySet()) {
				String filter[] = entry.getValue();
				
				Filter filterObj = new FilterBuilder()
										.setUserId(Integer.parseInt(filter[0]))
										.setTitle(filter[1])
									
										.setTo(filter[3])
										.setFrom(filter[4])
										.setBody(filter[5])
										.setFolderId(Integer.parseInt(filter[6]))
										.build();	
				filterObj.setSubject(filter[2]);
				filterObj.setId(entry.getKey());
				user.getFilters().add(filterObj);
			}
		} catch (DaoException e) {
			throw new ModelException(e.getMessage());
		}

		return true;
	}
	
	/**
	 * Return the user object
	
	 * @return User */
	public User getUser() {
		return user;
	}
	
	/**
	 * Set a user object.
	 * @param _usr
	 */
	public void  setUser(User _usr)
	{
		user=_usr;
	}

	/**
	 * Sets the currently active folder ID.
	 * @param id
	 */
	public void setCurrentFolderId(int id) {
		currentFolderId = id;
	}

	/**
	 * Returns current folder id
	
	 * @return id */
	public int getCurrentFolderId() {
		return currentFolderId;
	}

	/**
	 * Gets the current folder's name
	
	 * @return current folder name */
	public String getCurrentFolderName() {
		if (getCurrentFolderId() == -1 || getCurrentFolderId() == 0){
			return "Root";
		}
		String currentFolderName = user.getFolder(getCurrentFolderId()).getName();
		return currentFolderName;
	}

	/**
	 * Applies a particular filter and moves the emails
	 * @param filter
	 */
	public void applyFilter(Filter filter) {
		System.out.println("In apply filter");
		String to = filter.getTo();
		String from = filter.getFrom();
		String body = filter.getBody();
		
		Folder dest = user.getFolder(filter.getFolderId());
		
		// Add subject in the field
		List<Email> matchedEmails = user.searchEmails(to, from, "", body);
		
		// Loop through and delete the emails
		Set<Integer> emailIdsToMove = new HashSet<Integer>();
		
		// Get all the email ids to update in db
		for(Email email : matchedEmails){
//			emailIdsToMove.add(email.getId());
			HashSet<Integer> ids = new HashSet<Integer>();
			ids.add(email.getId());
			try {
				user.getFolder(email.getFolderId()).moveEmails(ids, dest);
			} catch (ModelException e) {
				// TODO Auto-generated catch block
//				e.printStackTrace();
			}
		}
		
		
		
	}
	/**
	 * Method to get the current status of server connectivity
	 * @return
	 */
	public boolean isConnected() {
		return isConnected;
	}
	/**
	 * @return the inStream
	 */
	public ObjectInputStream getInStream() {
		return inStream;
	}

	/**
	 * @return the osStream
	 */
	public ObjectOutputStream getOsStream() {
		return osStream;
	}

	/**
	 * @param inStream the inStream to set
	 */
	public void setInStream(ObjectInputStream inStream) {
		this.inStream = inStream;
	}

	/**
	 * @param osStream the osStream to set
	 */
	public void setOsStream(ObjectOutputStream osStream) {
		this.osStream = osStream;
	}

	/**
	 * Method to set the server status connectivity
	 * @param isConnected
	 */
	public void setConnected(boolean isConnected) {
		this.isConnected = isConnected;
	}

	public void setStreams(){
		
		try {
			inStream = new ObjectInputStream( serverSocket.getInputStream() );
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			osStream = new ObjectOutputStream( serverSocket.getOutputStream() );
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}
